B/listt: Lists Template. @Purpose: Code to support the list of... kind of value constructor. @------------------------------------------------------------------------------- @p Head. As ever: if there is no heap, there are no lists (in this sense). @c #IFDEF MEMORY_HEAP_SIZE; ! Will exist if any use is made of heap @p KOV Support. See the ``BlockValues.i6t'' segment for the specification of the following routines. @c [ LIST_OF_TY_Support task arg1 arg2 arg3; switch(task) { CREATE_KOVS: arg3 = LIST_OF_TY_Create(arg2); if (arg1) LIST_OF_TY_CopyRawArray(arg3, arg1, 2, 0); return arg3; CAST_KOVS: rfalse; DESTROY_KOVS: return LIST_OF_TY_Destroy(arg1); PRECOPY_KOVS: return LIST_OF_TY_PreCopy(arg1, arg2); COPY_KOVS: return LIST_OF_TY_Copy(arg1, arg2); COMPARE_KOVS: return LIST_OF_TY_Compare(arg1, arg2); READ_FILE_KOVS: rfalse; WRITE_FILE_KOVS: rfalse; HASH_KOVS: return LIST_OF_TY_Hash(arg1); } ]; @p Creation. A list is a multiple-block value with word-sized entries: the first few entries of the block are used for details about the list; the items in the the list then follow. Thus, to convert an item index to an array entry index, add |LIST_ITEM_BASE|. Lists are by default created empty but in a block-value with enough capacity to hold 26 items, this being what's left in a 32-word block once all overheads are taken care of: 4 words are consumed by the header, then 2 more by the list metadata entries below. @c Constant LIST_ITEM_KOV_F = 0; ! Entry 0: the kind of the list Constant LIST_LENGTH_F = 1; ! Entry 1: length, i.e., number of items Constant LIST_ITEM_BASE = 2; ! List items begin at this entry [ LIST_OF_TY_Create skov list; skov = KindBaseTerm(skov, 0); list = BlkAllocate(28*WORDSIZE, LIST_OF_TY, BLK_FLAG_MULTIPLE + BLK_FLAG_WORD); BlkValueWrite(list, LIST_ITEM_KOV_F, skov); BlkValueWrite(list, LIST_LENGTH_F, 0); return list; ]; @p Setting Up. NI needs to compile code which will create constant lists such as |{1, 4, 9}| at run-time: the following routine is convenient for that. A ``raw array'' in this routine's sense is an array |raw| such that |raw-->2| contains the number of items, |raw-->1| the kind of value, and |raw-->3| onwards are the items themselves. @c [ LIST_OF_TY_CopyRawArray list arr rea cast len i ex bk v w; if ((list==0) || (BlkType(list) ~= LIST_OF_TY)) return false; ex = BlkValueExtent(list); len = arr-->2; if ((len+LIST_ITEM_BASE > ex) && (BlkValueSetExtent(list, len+LIST_ITEM_BASE) == false)) return 0; BlkValueWrite(list, LIST_LENGTH_F, len); if (rea == 2) bk = BlkValueRead(list, LIST_ITEM_KOV_F); else { bk = arr-->1; BlkValueWrite(list, LIST_ITEM_KOV_F, bk); } for (i=0:i(i+3); if (KindAtomic(bk) == LIST_OF_TY) { w = LIST_OF_TY_Create(v-->1); LIST_OF_TY_CopyRawArray(w, v, 0, KindBaseTerm(bk, 0)); BlkValueWrite(list, i+LIST_ITEM_BASE, w); } else { if ((cast) && (cast ~= bk)) { if (KOVIsBlockValue(cast)) v = BlkValueCreate(cast, v, bk); } else { if (KOVIsBlockValue(bk)) v = BlkValueCreate(bk, v); } BlkValueWrite(list, i+LIST_ITEM_BASE, v); } } if ((cast) && (cast ~= bk)) BlkValueWrite(list, LIST_ITEM_KOV_F, cast); #ifdef SHOW_ALLOCATIONS; print "Copied raw array to list: "; LIST_OF_TY_Say(list, 1); print "^"; #endif; return list; ]; @p Destruction. If the list items are themselves block-values, they must all be freed before the list itself can be freed. @c [ LIST_OF_TY_Destroy list no_items i; if (KOVIsBlockValue(BlkValueRead(list, LIST_ITEM_KOV_F))) { no_items = BlkValueRead(list, LIST_LENGTH_F); for (i=0; i ex) { if (BlkValueSetExtent(list, len+LIST_ITEM_BASE) == false) return 0; } if (kov) BlkValueWrite(list, LIST_ITEM_KOV_F, kov); else BlkValueWrite(list, LIST_ITEM_KOV_F, OBJECT_TY); BlkValueWrite(list, LIST_LENGTH_F, len); obj = 0; for (i=0: i no_items+1))) { print "*** Couldn't add at entry ", posn, " in the list "; LIST_OF_TY_Say(list, true); print ", which has entries in the range 1 to ", no_items, " ***^"; RunTimeProblem(RTP_LISTRANGEERROR); rfalse; } ex = BlkValueExtent(list); if (no_items+LIST_ITEM_BASE+1 > ex) { if (BlkValueSetExtent(list, ex+16) == false) return 0; } if (KOVIsBlockValue(BlkValueRead(list, LIST_ITEM_KOV_F))) { nv = BlkValueCreate(BlkValueRead(list, LIST_ITEM_KOV_F)); BlkValueCopy(nv, v); v = nv; } if (posnflag) { posn--; for (i=no_items:i>posn:i--) { BlkValueWrite(list, i+LIST_ITEM_BASE, BlkValueRead(list, i-1+LIST_ITEM_BASE)); } BlkValueWrite(list, posn+LIST_ITEM_BASE, v); } else { BlkValueWrite(list, no_items+LIST_ITEM_BASE, v); } BlkValueWrite(list, LIST_LENGTH_F, no_items+1); return list; ]; @p Append List. Instead of adjoining a single value, we adjoin an entire second list, which must be of a compatible kind of value (something which NI's type-checking machinery polices for us). Except that we have a list |more| rather than a value |v| to insert, the specification is the same as for |LIST_OF_TY_InsertItem|. @c [ LIST_OF_TY_AppendList list more posnflag posn nodups v i j no_items msize ex nv; if ((list==0) || (BlkType(list) ~= LIST_OF_TY)) return false; if ((more==0) || (BlkType(more) ~= LIST_OF_TY)) return list; no_items = BlkValueRead(list, LIST_LENGTH_F); if ((posnflag) && ((posn<1) || (posn > no_items+1))) { print "*** Couldn't add at entry ", posn, " in the list "; LIST_OF_TY_Say(list, true); print ", which has entries in the range 1 to ", no_items, " ***^"; RunTimeProblem(RTP_LISTRANGEERROR); rfalse; } msize = BlkValueRead(more, LIST_LENGTH_F); ex = BlkValueExtent(list); if (no_items+msize+LIST_ITEM_BASE > ex) { if (BlkValueSetExtent(list, no_items+msize+LIST_ITEM_BASE+8) == false) return 0; } if (posnflag) { posn--; for (i=no_items+msize:i>=posn+msize:i--) { BlkValueWrite(list, i+LIST_ITEM_BASE, BlkValueRead(list, i-msize+LIST_ITEM_BASE)); } ! BlkValueWrite(list, posn, v); for (j=0: j to) || (from <= 0) || (to > no_items)) { if (forgive) { if (from <= 0) from = 1; if (to >= no_items) to = no_items; if (from > to) return list; } else { print "*** Couldn't remove entries ", from, " to ", to, " from the list "; LIST_OF_TY_Say(list, true); print ", which has entries in the range 1 to ", no_items, " ***^"; RunTimeProblem(RTP_LISTRANGEERROR); rfalse; } } to--; from--; d = to-from+1; if (KOVIsBlockValue(BlkValueRead(list, LIST_ITEM_KOV_F))) for (i=0; i ex) { if (BlkValueSetExtent(list, newsize+LIST_ITEM_BASE) == false) return 0; } dv = DefaultValueOfKOV(BlkValueRead(list, LIST_ITEM_KOV_F)); for (i=no_items: i newsize) { if (this_way_only == 1) return list; if (truncation_end == -1) { if (KOVIsBlockValue(BlkValueRead(list, LIST_ITEM_KOV_F))) for (i=0: ino_items)) { if (forgive) return false; print "*** Couldn't read from entry ", i, " of a list which"; switch (no_items) { 0: print " is empty ***^"; 1: print " has only one entry, numbered 1 ***^"; default: print " has entries numbered from 1 to ", no_items, " ***^"; } RunTimeProblem(RTP_LISTRANGEERROR); if (no_items >= 1) i = 1; else return false; } return BlkValueRead(list, LIST_ITEM_BASE+i-1); ]; @p Write Item. The slightly odd name for this function comes about because our usual way to convert an rvalue such as |LIST_OF_TY_GetItem(L, 4)| is to prefix |Write|, so that it becomes |WriteLIST_OF_TY_GetItem(L, 4)|. @c [ WriteLIST_OF_TY_GetItem list i val no_items; if ((list==0) || (BlkType(list) ~= LIST_OF_TY)) return false; no_items = BlkValueRead(list, LIST_LENGTH_F); if ((i<=0) || (i>no_items)) { print "*** Couldn't write to list entry ", i, " of a list which"; switch (no_items) { 0: print " is empty ***^"; 1: print " has only one entry, numbered 1 ***^"; default: print " has entries numbered from 1 to ", no_items, " ***^"; } return RunTimeProblem(RTP_LISTRANGEERROR); } BlkValueWrite(list, LIST_ITEM_BASE+i-1, val); ]; @p Put Item. Higher-level code should not use |Write_LIST_OF_TY_GetItem|, because it does not properly keep track of block-value copying: the following should be used instead. @c [ LIST_OF_TY_PutItem list i v no_items nv; if ((list==0) || (BlkType(list) ~= LIST_OF_TY)) return false; no_items = BlkValueRead(list, LIST_LENGTH_F); if (KOVIsBlockValue(BlkValueRead(list, LIST_ITEM_KOV_F))) { nv = BlkValueCreate(BlkValueRead(list, LIST_ITEM_KOV_F)); BlkValueCopy(nv, v); v = nv; } if ((i<=0) || (i>no_items)) return false; BlkValueWrite(list, LIST_ITEM_BASE+i-1, v); ]; @p Multiple Object List. The parser uses one data structure which is really a list: but which can't be represented as such because the heap might not exist. This is the multiple object list, which is used to handle commands like TAKE ALL by firing off a sequence of actions with one of the objects taken from entries in turn of the list. The following converts it to a list structure. @c [ LIST_OF_TY_Mol list len i; if ((list==0) || (BlkType(list) ~= LIST_OF_TY)) return 0; len = multiple_object-->0; LIST_OF_TY_SetLength(list, len); for (i=1: i<=len: i++) LIST_OF_TY_PutItem(list, i, multiple_object-->i); return list; ]; [ LIST_OF_TY_Set_Mol list len i; if ((list==0) || (BlkType(list) ~= LIST_OF_TY)) return 0; len = BlkValueRead(list, LIST_LENGTH_F); if (len > 63) len = 63; multiple_object-->0 = len; for (i=1: i<=len: i++) multiple_object-->i = BlkValueRead(list, LIST_ITEM_BASE+i-1); ]; @p Reversing. Reversing a list is, happily, a very efficient operation when the list contains block-values: because the pointers are rearranged but none is duplicated or destroyed, we can for once ignore the fact that they are pointers to block-values and simply move them around like any other data. @c [ LIST_OF_TY_Reverse list no_items i v; if ((list==0) || (BlkType(list) ~= LIST_OF_TY)) return 0; no_items = BlkValueRead(list, LIST_LENGTH_F); if (no_items < 2) return list; for (i=0;i*20:i--) BlkValueWrite(list, LIST_ITEM_BASE+i, BlkValueRead(list, LIST_ITEM_BASE+i-1)); BlkValueWrite(list, LIST_ITEM_BASE, v); } return list; ]; @p Sorting. And the same, again, is true of sorting: but we do have to take note of block values when it comes to performing comparisons, because we can only compare items in the list by looking at their contents, not the pointers to their contents. |LIST_OF_TY_Sort(list, dir, prop)| sorts the given |list| in ascending order if |dir| is 1, in descending order if |dir| is $-1$, or in random order if |dir| is 2. The comparison used is the one for the kind of value stored in the list, unless the optional argument |prop| is supplied, in which case we sort based not on the item values but on their values for the property |prop|. (This only makes sense if the list contains objects.) @c Global LIST_OF_TY_Sort_cf; [ LIST_OF_TY_Sort list dir prop cf i j no_items v; no_items = BlkValueRead(list, LIST_LENGTH_F); if (dir == 2) { if (no_items < 2) return; for (i=1:i